//
//  lib_fsys.c
//  LuaEx
//
//  Created by HJC on 11-11-18.
//  Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
//

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>



typedef enum
{
    file_type_reg = 0x001,
    file_type_dir = 0x002,
    file_type_link = 0x004,
} file_type;


typedef enum
{
    traverse_type_stop,
    traverse_type_stepin,
    traverse_type_stepover,
} traverse_type;




/*************************/
static unsigned int _fileType(const char* mode)
{
    unsigned int fileType = 0;
    for (const char* p = mode; *p; p++)
    {
        switch (*p) 
        {
            case 'd':   fileType |= file_type_dir;  break;
            case 'f':   fileType |= file_type_reg;  break;
            case 'l':   fileType |= file_type_link;  break;
            default:    break;
        }
    }
    return fileType;
}



static int fsys_dir(lua_State* lua)
{
    const char* path = luaL_checkstring(lua, 1);    
    unsigned int fileType = file_type_reg | file_type_dir | file_type_link;
    int num = lua_gettop(lua);
    if (num >= 2)
    {
        fileType = _fileType(luaL_checkstring(lua, 2));
    }
    
    DIR* dir = opendir(path);
    if (dir == NULL)
    {
        lua_pushnil(lua);
        lua_pushstring(lua, strerror(errno));
        return 2;
    }
    
    lua_newtable(lua);
    int i = 1;
    struct dirent* entry = NULL;
    while ((entry = readdir(dir)) != NULL)
    {
        if (entry->d_type == DT_DIR)
        {
            if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            {
                continue;
            }
        }
        
        unsigned int testFlag = 0;
        switch (entry->d_type) 
        {
            case DT_DIR:    testFlag = file_type_dir;   break;
            case DT_REG:    testFlag = file_type_reg;   break;
            case DT_LNK:    testFlag = file_type_link;  break;
            default:        break;
        }
        
        if (fileType & testFlag)
        {
            lua_pushnumber(lua, i++);
            lua_pushstring(lua, entry->d_name);
            lua_settable(lua, -3);
        }
    }
    
    closedir(dir);
    return 1;
}




static const char* new_path(const char* basePath, const char* refPath)
{
    size_t len0 = strlen(basePath);
    size_t len1 = strlen(refPath);
    
    char* buf = (char*)malloc(len0 + len1 + 2);
    strncpy(buf, basePath, len0);
    
    if (len0 > 0)
    {
        if (buf[len0 - 1] != '/')
        {
            buf[len0] = '/';
            len0++;
            buf[len0] = 0;
        }
        else
        {
            while (len0 - 1 > 0 && (buf[len0 - 2] == '/'))
            {
                len0--;
                buf[len0] = 0;
            }
        }
    }
    
    strncpy(buf + len0, refPath, len1);
    buf[len0 + len1] = 0;
    return buf;
}




static traverse_type tra_callback(struct dirent* entry, const char* basePath, const char* refPath, lua_State* lua, int level)
{
    int top = lua_gettop(lua);
    lua_pushvalue(lua, 2);
    
    lua_newtable(lua);
    lua_pushstring(lua, "name");
    lua_pushstring(lua, entry->d_name);
    lua_settable(lua, -3);
    
    lua_pushstring(lua, "type");
    switch (entry->d_type) 
    {
        case DT_DIR:    lua_pushstring(lua, "d");   break;
        case DT_REG:    lua_pushstring(lua, "f");   break;
        case DT_LNK:    lua_pushstring(lua, "l");   break;
        default:        break;
    }
    lua_settable(lua, -3);
    
    lua_pushstring(lua, "base");
    lua_pushstring(lua, basePath);
    lua_settable(lua, -3);
    
    lua_pushstring(lua, "ref");
    lua_pushstring(lua, refPath);
    lua_settable(lua, -3);
    
    lua_pushstring(lua, "level");
    lua_pushinteger(lua, level);
    lua_settable(lua, -3);
    
    for (int index = 3; index <= top; index++)
    {
        lua_pushvalue(lua, index);
    }
    lua_call(lua, (top - 3) + 2, 1);
    
    traverse_type result = traverse_type_stepover;
    if (lua_isstring(lua, -1))
    {
        const char* str = lua_tostring(lua, -1);
        if (!strcmp(str, "over"))
        {
            result = traverse_type_stepover;
        }
        else if (!strcmp(str, "in"))
        {
            result = traverse_type_stepin;
        }
        else if (!strcmp(str, "stop"))
        {
            result = traverse_type_stop;
        }
    }
    
    lua_settop(lua, top);
    
    return result;
}





static traverse_type traverse_path(const char* basePath, const char* refPath, lua_State* lua, int level)
{
    const char* path = new_path(basePath, refPath);
    DIR* dir = opendir(path);
    free((void*)path);
    path = NULL;
    
    if (dir == NULL)
    {
        return traverse_type_stepin;
    }
    
    struct dirent* entry = NULL;
    while ((entry = readdir(dir)) != NULL)
    {
        if (entry->d_type == DT_DIR)
        {
            if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            {
                continue;
            }
        }
        
        unsigned int testFlag = 0;
        switch (entry->d_type) 
        {
            case DT_DIR:    testFlag = file_type_dir;   break;
            case DT_REG:    testFlag = file_type_reg;   break;
            case DT_LNK:    testFlag = file_type_link;  break;
            default:        break;
        }
        
        if (testFlag)
        {
            path = new_path(refPath, entry->d_name);
            traverse_type type = tra_callback(entry, basePath, path, lua, level);
            if (type == traverse_type_stepin && entry->d_type == DT_DIR)
            {
                type = traverse_path(basePath, path, lua, level + 1);
            }
            free((void*)path);
            
            if (type == traverse_type_stop)
            {
                closedir(dir);
                return traverse_type_stop;
            }
        }
    }
    
    closedir(dir);
    return traverse_type_stepin;
}




static int fsys_tdir(lua_State* lua)
{
    const char* path = luaL_checkstring(lua, 1);  
    luaL_checktype(lua, 2, LUA_TFUNCTION);
    
    const char* basePath = new_path(path, "");
    traverse_path(basePath, "", lua, 1);
    free((void*)basePath);
    return 0;
}


/********************/

static const struct luaL_Reg fsyslibs[] = {
    { "dir", fsys_dir },
    { "tdir", fsys_tdir },
    { NULL, NULL }
};



extern int luaopen_fsys(lua_State* lua);
int luaopen_fsys(lua_State* lua)
{
    luaL_register(lua, "fsys", fsyslibs);
    return 1;
}
